package org.springshowcase.core.mvc;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.collections.IterableMap;
import org.apache.commons.collections.MapIterator;
import org.apache.commons.collections.map.HashedMap;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.NoSuchMessageException;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import org.springshowcase.core.commons.utils.BeansUtils;
import org.springshowcase.core.commons.utils.CollectionUtils;
import org.springshowcase.core.commons.utils.ControllerUtils;
import org.springshowcase.core.commons.utils.ExceptionsUtils;
import org.springshowcase.core.commons.utils.FilesUtils;
import org.springshowcase.core.commons.utils.StringUtils;

// TODO: Auto-generated Javadoc
/**
 * The Class ExceptionResolver.
 */
public class ExceptionResolver implements HandlerExceptionResolver {

	// ---------------------------------------------------------------------
	// 멤버 변수 선언
	// ---------------------------------------------------------------------
	/** The Constant log. */
	private static final Logger logger = LoggerFactory
			.getLogger(ExceptionResolver.class);

	private static final String EXCEPTION_LOG_FILE_EXT = ".log";

	/** The Constant EXCEPTION_LOG_FILE_ENCODING. */
	private static final String EXCEPTION_LOG_FILE_ENCODING = "UTF-8";

	/** The error view. */
	private String errorView;

	/** The scan package. */
	private List<String> scanPackage;

	/** The log file path. */
	private String logFilePath;

	/** The system id. */
	private String systemId;

	/** The message source accessor. */
	@Autowired
	private MessageSourceAccessor messageSourceAccessor;

	// ---------------------------------------------------------------------
	// 생성자 함수 선언
	// ---------------------------------------------------------------------
	// ---------------------------------------------------------------------
	// DI 메서드 (setter,getter)
	// ---------------------------------------------------------------------

	/**
	 * Sets the error view.
	 * 
	 * @param errorView the new error view
	 */
	public final void setErrorView(String errorView) {
		this.errorView = errorView;
	}

	/**
	 * Sets the scan package.
	 * 
	 * @param scanPackage the scanPackage to set
	 */
	public void setScanPackage(List scanPackage) {
		this.scanPackage = scanPackage;
	}

	/**
	 * Sets the message source accessor.
	 * 
	 * @param messageSourceAccessor the new message source accessor
	 */
	public void setMessageSourceAccessor(
			MessageSourceAccessor messageSourceAccessor) {
		this.messageSourceAccessor = messageSourceAccessor;
	}

	/**
	 * @param logFilePath the logFilePath to set
	 */
	public void setLogFilePath(String logFilePath) {
		this.logFilePath = logFilePath;
	}

	// ---------------------------------------------------------------------
	// 상속 인터페이스 구현 메서드
	// ---------------------------------------------------------------------

	/**
	 * @param systemId the systemId to set
	 */
	public void setSystemId(String systemId) {
		this.systemId = systemId;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.springframework.web.servlet.HandlerExceptionResolver#resolveException
	 * (javax.servlet.http.HttpServletRequest,
	 * javax.servlet.http.HttpServletResponse, java.lang.Object,
	 * java.lang.Exception)
	 */
	public final ModelAndView resolveException(HttpServletRequest request,
			HttpServletResponse response, Object object, Exception e) {
		logger.info("invoke exception ....");
		ModelAndView mav = new ModelAndView();
		mav.setViewName(this.errorView);
		logger.error(String.valueOf(System.currentTimeMillis()), e);

		StringBuffer exceptionPakageInspect = new StringBuffer();
		String[] exceptionTrace = ExceptionsUtils.getRootCauseStackTrace(e);
		for (String m : exceptionTrace) {

			if (this.scanPackage != null || this.scanPackage.size() > 0) {
				if (isContainException(m)) {
					exceptionPakageInspect.append(
							StringUtils.substringBetween(m, "(", ")")).append(
							",");
				}
			}
			else {
				exceptionPakageInspect.append(
						StringUtils.substringBetween(m, "(", ")")).append(",");
			}
		}

		// 수집된 정보를 파싱해서 역순으로 재정렬 한다.
		StringTokenizer tokenizer = new StringTokenizer(exceptionPakageInspect
				.toString(), ",");
		if (tokenizer.countTokens() > 0) {
			exceptionPakageInspect.setLength(0);
			exceptionTrace = new String[tokenizer.countTokens()];
			int i = 0;
			while (tokenizer.hasMoreTokens()) {
				exceptionTrace[i] = tokenizer.nextToken();
				if (i > 0) {
					exceptionTrace[i] += " <<< ";
				}
				i++;
			}
			CollectionUtils.arrayReverse(exceptionTrace);
			for (String s : exceptionTrace) {
				exceptionPakageInspect.append(s);
			}
		}

		// exception VO 설정
		ExceptionLogBean elb = new ExceptionLogBean();
		elb.setSystemId(StringUtils.trimToEmpty(this.systemId));
		elb.setExceptionUrl(request.getRequestURI());
		String exceptionName = ExceptionsUtils.getRootCauseMessage(e);
		int idx = exceptionName.indexOf(":");
		if (idx > 0) {
			exceptionName = exceptionName.substring(0, idx);
		}
		elb.setExceptionName(exceptionName);
		elb.setExceptionTrace(exceptionPakageInspect.toString());
		IterableMap parameterMap = new HashedMap(request.getParameterMap());
		MapIterator mi = parameterMap.mapIterator();
		String requestParams = "";
		while (mi.hasNext()) {
			String key = (String) mi.next();
			String[] values = (String[]) mi.getValue();
			requestParams += key + "=" + ArrayUtils.toString(values) + ",";
		}
		elb.setExceptionParams(requestParams);
		elb
				.setClientIp(request.getRemoteHost() + ":"
						+ request.getRemotePort());
		elb
				.setServerIp(request.getServerName() + ":"
						+ request.getServerPort());
		// logfile write
		Date today = new java.util.Date();
		SimpleDateFormat logFormat = new SimpleDateFormat("yyyy-MM-dd H:m:s");
		String currentTime = logFormat.format(today);
		SimpleDateFormat fileFormat = new SimpleDateFormat("yyyy-MM-dd");
		String fileName = fileFormat.format(today) + EXCEPTION_LOG_FILE_EXT;

		String exceptionKey = String.valueOf(System.currentTimeMillis());
		String traceMessage = BeansUtils.toStringBean(elb);
		traceMessage = traceMessage.substring(traceMessage.indexOf("["));

		StringBuffer loggingMeesage = new StringBuffer();
		loggingMeesage
				.append("[" + currentTime + "] (" + exceptionKey + ") : ")
				.append(traceMessage);
		BufferedWriter out = null;
		try {
			FileWriter fstream = new FileWriter(FilesUtils.getConcatFilePath(
					this.logFilePath, "/", fileName), true);
			out = new BufferedWriter(fstream);
			out.write(loggingMeesage.toString()+"\r");
			out.flush();
			// Close the output stream
		}catch (IOException ioe) {
			// exception 발생시 log4로 출력 한다.
			logger.error(loggingMeesage.toString());
		}finally{
			if(out != null) {
				try{out.close();}catch(Exception ie){}
			}
		}
		
		//response setting
		elb.setExceptionMessage(this.messageSourceAccessor.getMessage(exceptionName, "System Error"));
		mav.addObject(elb);
		return mav;
	}

	/**
	 * Checks if is contain exception.
	 * 
	 * @param exception the exception
	 * @return true, if is contain exception
	 */
	private boolean isContainException(String exception) {

		for (String str : this.scanPackage) {
			if (StringUtils.contains(exception, str)) {
				return true;
			}
		}
		return false;
	}

	// ---------------------------------------------------------------------
	// self 메서드 (private,pulbic)
	// ---------------------------------------------------------------------

	// ---------------------------------------------------------------------
	// 추상 클래스 구현 메서드
	// ---------------------------------------------------------------------
	// ---------------------------------------------------------------------
	// 서브 클래스 추상 메서드 선언
	// ---------------------------------------------------------------------
}
